package cn.zhqr.core.interceptor;

import cn.zhqr.common.external.cache.CacheManager;
import cn.zhqr.common.external.cache.RedisCacheManager;
import cn.zhqr.common.util.digest.MD5Utils;
import cn.zhqr.common.util.str.StringUtils;
import cn.zhqr.core.interceptor.annotation.CacheStatement;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.JFinal;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.jfinal.render.JsonRender;
import com.jfinal.render.Render;

import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

/**
 * 缓存请求返回结果，将接口数据进行缓存，减轻数据库压力
 * 缓存key默认由访问路径+参数md5自动生成，过期时间默认一个小时。
 * 如需更改默认设置，可配合CacheStatement注解使用。
 * Created by yantingjun on 2018/6/13.
 */
public class JsonCacheInterceptor implements Interceptor{
    private static final Log log = Log.getLog(JsonCacheInterceptor.class);


    private static String cacheName = "json_cache";//缓存名称、key值前缀
    private static int defaultExpire = 3600;//缓存时间，默认一个小时
    private static String separator = "_";//key值分隔符

    private static CacheManager cacheManager = new RedisCacheManager(cacheName);


    @Override
    public void intercept(Invocation inv) {
        String cacheKey = "";
        int expire = 0;

        CacheStatement cacheStatement = inv.getMethod().getDeclaredAnnotation(CacheStatement.class);
        if (cacheStatement == null) {
            cacheStatement = inv.getController().getClass().getDeclaredAnnotation(CacheStatement.class);
        }

        if(cacheStatement != null){
            cacheKey = getCacheKeyByAnnotation(inv,cacheStatement);
            expire = cacheStatement.expire();
        }

        if(expire <= 0){
            expire = defaultExpire;
        }

        if(StrKit.isBlank(cacheKey)){
            cacheKey = buildDefaultCacheKey(inv);
        }

        Object value = cacheManager.get(cacheKey);
        if(value != null){
            inv.getController().renderText(String.valueOf(value));
            log.info(cacheKey+" 命中缓存 key:"+cacheKey+" value:"+value);
            return;
        }

        inv.invoke();

        Render render = inv.getController().getRender();
        if(render instanceof JsonRender){
            String json = ((JsonRender) render).getJsonText();
            if(StrKit.notBlank(json)){
                cacheManager.setEx(cacheKey,expire,json);
                log.info("存入缓存 key:"+cacheKey+" expire:"+expire+" value:"+json);
            }
        }
    }


    /**
     * 获取注解中声明的缓存key
     * @param inv
     * @return
     */
    private String getCacheKeyByAnnotation(Invocation inv, CacheStatement cacheStatement) {
        String cacheKey = "";
        if(cacheStatement != null && StrKit.notBlank(cacheStatement.key())){
            cacheKey = cacheStatement.key();

            //参数占位符替换为实际参数值
            Enumeration<String> paramNames =  inv.getController().getParaNames();
            while (cacheKey.contains("{") && paramNames.hasMoreElements()){
                String paramName = paramNames.nextElement();
                cacheKey = cacheKey.replace("{"+paramName+"}", inv.getController().getPara(paramName));
            }

            //url参数占位符替换为实际参数值
            if(cacheKey.contains("{")){
                String urlPara = inv.getController().getPara();
                if(StrKit.notBlank(urlPara)){
                    cacheKey = cacheKey.replace("{url_para}", urlPara);
                    String[] urlParaArray = urlPara.split(JFinal.me().getConstants().getUrlParaSeparator());

                    for(int i=0;i<urlParaArray.length && cacheKey.contains("{");i++){
                        cacheKey = cacheKey.replace("{url_para"+i+"}", urlParaArray[i]);
                    }
                }
            }
            if(cacheKey.contains("{action_key}")){
                cacheKey = cacheKey.replace("{action_key}", inv.getActionKey());
            }
            if(cacheKey.contains("{param_digest}")){
                cacheKey = cacheKey.replace("{param_digest}", getParamDigest(inv));
            }

            //未匹配到的占位符替换为空
            if(cacheKey.contains("{")){
                cacheKey = cacheKey.replaceAll("\\{[^\\}]*\\}","NULL");
            }
        }
        return cacheKey;
    }

    /**
     * 通过访问路径+参数md5构造默认的缓存key
     * @param inv
     * @return
     */
    private String buildDefaultCacheKey(Invocation inv) {
        String path = inv.getController().getRequest().getRequestURI();

        StringBuilder keySB =  new StringBuilder(cacheName)
                .append(path.replace("/",separator));

        String paramDigest = getParamDigest(inv);
        if(StrKit.notBlank(paramDigest)){
            keySB.append(separator).append(paramDigest);
        }
        return keySB.toString();
    }

    /**
     * 提取请求参数摘要信息
     * @param inv
     * @return
     */
    private String getParamDigest(Invocation inv){
        //请求参数按key字母大小排序
        TreeMap<String,String>  orderParamMap = new TreeMap();
        for(Map.Entry<String,String[]> entry : inv.getController().getParaMap().entrySet()){
            orderParamMap.put(entry.getKey(), StringUtils.join(entry.getValue(),","));
        }

        //将请求参数组装成key1=value1&key2=value2的形式
        StringBuilder paramSB = new StringBuilder();
        for(Map.Entry<String,String> entry : orderParamMap.entrySet()){
            if(paramSB.length()!=0){
                paramSB.append("&");
            }
            paramSB.append(entry.getKey()).append("=").append(entry.getValue());
        }

        if(paramSB.length() > 0){
            return MD5Utils.md5(paramSB.toString());
        }
        return "";
    }

    public static String getCacheName() {
        return cacheName;
    }

    public static void setCacheName(String cacheName) {
        JsonCacheInterceptor.cacheName = cacheName;
    }

    public static int getDefaultExpire() {
        return defaultExpire;
    }

    public static void setDefaultExpire(int defaultExpire) {
        JsonCacheInterceptor.defaultExpire = defaultExpire;
    }

    public static CacheManager getCacheManager() {
        return cacheManager;
    }

    public static void setCacheManager(CacheManager cacheManager) {
        JsonCacheInterceptor.cacheManager = cacheManager;
    }
}
